#title: 表达式引擎简介
#author:zozoh(zozohtnt@gmail.com)
#author:wendal(wendal1985@gmail.com)
#author:juqkai(juqkai@gmail.com)
#index:0,1
------------------------------------------------------------------------
为什么需要 EL 表达式引擎

    你，对，就是你，正在看这篇文章的人，我虽然不认识你，但是 我可以负责任的说，
    如果你看到这个标题就心里在悄悄的呼喊：
    {#008800;*“靠，他们连这个都有！我省事了，哇哈哈哈和”}。
    那么，你绝对属于百分之一的特例。 就是说，绝大多数人的绝大多数项目，是不需要
    一个嵌入式的表达式引擎的。因此，提供这个功能的目的就是：

    {#0000AA;* 满足一小撮人的一小撮要求}

    但是，"一小撮人"的"一小撮要求"有很多，作为一个小众类库，为什么单单打算支持这个特性呢？
    下面是我的理由：
     * 这个功能是其它模块功能的基础，我们需要它
     * 可能因此吸引其他的开发者对 Nutz 的兴趣
         * 需要嵌入式表达式引擎的人是 Java 开发者的少数人，但是这些人也应该比 Nutz 的用户要多
         * 这些人基本上编程水平要强一些
     * 其他的提交者对增加这个特性没有特别强烈的反对

------------------------------------------------------------------------
近一步介绍表达式引擎

    那么它怎么使用呢？
    
    是的，我想这可能会是你脑海里闪出的第一个问题。并且，我想你真正想问的是:“它好用吗？”

    如果你脑海里第一个问题不是这个，而是：“表达式引擎是神马东东？” 那么建议你不用阅读本文了，
    反正你也用不着。等你需要的时候，再读也不迟，反正这篇文章又不长。
    
    而关于 {*"好用"}，还有下面这三层含义：
    
    它容易使用吗？
        {{{<Java>
        System.out.println(El.eval("3+4*5").equals(23));  // 将打印 true，够简单吧
        }}}
        表达式接受的是字符串输入，输出则是一个Object对象，而Object对象本身是根据计算结果进行进行了自动封装的。
    
    它功能强大吗？
    
        虽然在 [#一些表达式的例子] 这一节我有更详细的例子，但是这里我必须要先概括几点：
        
        {*它支持变量}，比如
        {{{<Java>
        Context context = Lang.context();
        context.set("a", 10);
        System.out.println(El.eval(context, "a*10"));  // 将打印 100 
        }}}
        通过 Context 接口，你可以为你的表达式随意设置变量的值。它支持如下类型的 Java 数据
         * 整型 - int 或  Integer
         * 浮点 - float 或 Float
         * 长整 - long 或 Long
         * 布尔 - boolean 或 Boolean
         * 字符串 - String
         * 数组 - `T[]`
         * 列表 - `Lst<Ti>`
         * 集合 - `Collection<T>`
         * Map - `Map<String,?>`
         * 普通 Java 对象
        基本上，有了这些，你可以为所欲为了吧。
    
    它速度怎么样？
        
        我觉得它速度不怎么样。它的工作的原理是这样的，每次解析都经过如果下三步
         # 解析成后缀表达式形式的一个队列
         # 将后缀表达式解析成一棵运算树.
         # 对运算树的根结点进行运算.
	
        当然我也提供了一个提升效率的手段，因为如果每次计算都经过这三个步骤当然慢，
        所以我们可以对它先预编译：
        {{{<Java>
	    El exp = new El("a*10");  // 预编译结果为一个 El 对象
	
        Context context = Lang.context();
        context.set("a", 10);

        System.out.println(exp.eval(context));  // 将打印 100 
        }}}
	
        El在实例化时就会对表达式进行预编译，会直接编译成运算树，当调用eval方法时，
        就不用再耗时的编译动作了.
        
        它的 eval 函数是线程安全的，只要在多个线程内给它不同的 context 就是了。
        当然，你也可以在多个线程间共享同一个 Context，那运行起来一定很有趣，不是吗？

------------------------------------------------------------------------
支持什么样的操作符
    
    我想但凡有机会和兴趣读到这篇文字的同学，一定是编程老手，即使是自称{*小白}的的同学们，
    你们对一个编程语言应该支持的操作符基本都差不多熟的不行，
    所以，我就不在这里唠叨操作符的具体细节了，我只给一个列表，告诉你我现在支持什么操作符。
    
    另外，再加上一句：
    
    {*只要支持的操作符，我会让它的优先级以及行为会和 Java 的表达式一致。如果你发现不一致\
    别犹豫，给我报 Issue 吧。}

    || 符号       || 权重 ||   解释       ||
    || `()`       || 100  || 括号，优先计算   ||
    || `,`        || 0    || 逗号，主要是方法参数   ||
    || `.`        || 1    || 访问对象的属性，或者Map的值，或者方法调用，或者自定义函数调用（需要结合后面是否有括号） ||
    || `['abc']`  || 1    || Java 对象 Map按键值获得值   ||
    || `[3]`      || 1    || 数字，列表，或者集合的下标访问符号  ||
    || `*`        || 3    || 乘           ||
    || `/`        || 3    || 整除         ||
    || `%`        || 3    || 取模         ||
    || `+`        || 4    || 加           ||
    || `-`        || 4    || 减           ||
    || `-`        || 2    || 负           ||
    || `>=`       || 6    || 大于等于     ||
    || `<=`       || 5    || 小于等于     ||
    || `==`       || 7    || 等于         ||
    || `!=`       || 6    || 不等于       ||
    || `!`        || 7    || 非           ||
    || `>`        || 6    || 大于         ||
    || `<`        || 6    || 小于         ||
    || `&&`       || 11   || 逻辑与       ||
    || `| |`       || 12   || 逻辑或       ||
    || `?:`       || 13   || 三元运算     ||
    || `&`        || 8    || 位运算，与    ||
    || `~`        || 2    || 位运算，非    ||
    || `|`        || 10   || 位运算，或    ||
    || `^`        || 9    || 位运算，异或  ||
    || `<<`       || 5    || 位运算，左移  ||
    || `>>`       || 5    || 位运算，右移  ||
    || `>>>`      || 5    || 位运算，无符号右移  ||
    || `&`        || 8    || 位运算，与    ||

    当然，同任何编程语言一样，表达式也支持 左括号 {*#00A;`(`} 以及 右括号{*#00A;`)`}，
    来控制表达式的的计算优先级别

------------------------------------------------------------------------
自定义函数

	好吧, 你肯定要说上面的功能简直弱爆了, 就一点简单的加加减减有什么好稀奇的, 
	再强点的需求就没办法满足了.
	确实是这样, 所以, 我们在 EL 里面添加了自定义函数, 
    嘿嘿, 这回强了吧. 言归正传,下面详细的说说它的使用, 以及怎么自定义.
	
	现有的自定义函数:
	|| 名称 || 参数           || 解释                 || 例子                 ||
	|| max  || 任意个Number型 || 取出参数中最大值     || max(1, 2, 3, 4)=>4   ||
	|| min  || 任意个Number型 || 取出参数中最小值     || min(1, 2, 3, 4)=>1   ||
	|| trim || 一个String     || 去掉字符串两边的空格 || trim("   1  ")=> "1" ||
	
	使用很简单吧.

	当EL里面的功能无法满足你的需求时, 你可以自定义一些功能来实现, 怎么自定义呢? 
    {{{
    # 创建一个类, 使它实现 org.nutz.el.opt.RunMethod, org.nutz.plugin.Plugin 这两个接口.
    # 在配置文件中添加一个配置项:
    {
    "EL": {"custom":[...函数列表...]}
    }
    # 如果你只使用了EL这一个模块, 没有其它模块, 并且在其它地方都没有加载过配置, 请使用下面的语句加载配置:
    NutConf.load("配置文件");
    //加载自定义函数配置, 必须!因为默认情况下, CustomMake只在static{}块中加载一次配置, 新添加的配置在static之后的话, 那么新添加的函数配置将不会生效!
    CustomMake.init();
    }}}

    完成这两步后, 你就定义了你自己的函数. 看个trim的例子:
    {{{<JAVA>
    public class Trim implements RunMethod, Plugin{
        //处理方法, fetchParam为函数的参数. 它会将EL中函数括号后的所有内容传递过来
        public Object run(List<Object> fetchParam) {
            if(fetchParam.size() <= 0){
            throw new ElException("trim方法参数错误");
            }
            String obj = (String) fetchParam.get(0);
            return obj.trim();
        }
        //是否可以执行
        public boolean canWork() {
            return true;
        }
        //在EL表达式中的函数名
        public String fetchSelf() {
            return "trim";
        }
    }
    }}}

------------------------------------------------------------------------
还有些什么功能?
    
	其实我们的 EL 很强悍的, 有好多功能, 好多使用技巧等待着你的发现. 现在简单的罗列一下, EL中的一些特性吧. 
    
    忠于 Java 
        Nutz中的 EL 完全忠实于 JAVA 基本运算规则, 并没有做一些扩展, 比如最常见的, 数据类型转换,
        在 JAVA 中进行数值运算的过程中, 是根据运算符两边的类型而最终决定运算结果的类型的, 
        比如: 
        {{{
        7/3  // 将返回int型
        而 
        (1.0 * 7)/3    // 返回double
        (1.0f * 7)/3   // 则返回float
        }}}
        为什么后面两个返回类型不一样呢?  因为在 JAVA 中默认浮点类型是 double 哦.

        基于这个原因, 在 EL 中同样保留了这些特点. 所以, 亲, 要是没返回 double 别骂我们哦~~~

    支持对象方法调用
        亲, EL 支持对象, 支持对象方法调用哦~~~

        这有什么好大不了的? 给你看个例子, 嘿嘿:
        {{{<JAVA>
        Context context = Lang.context();
        context.set("a", new BigDecimal("7"));
        context.set("b", new BigDecimal("3"));
        assertEquals(10, El.eval(context, "a.add(b).intValue()"));
        }}}
         * 看到什么没? 对了, BigDecimal 你完全可以丢各种各样的对象到 context 里面去, 然后在 EL 中调用它们的方法. 
         * 然后你就进行各种各样的虐待, 皮鞭, 蜡烛, 想来啥来啥...额...太邪恶了...
         * 同样, EL 是使用反射的, 所以会存在一些底层异常的问题. 

    支持静态方法调用
        没看错, EL支持静态方法调用, 不过, 在调用之前你需要设置一个变量才行...
	{{{<java>
	Context context = Lang.context();
	context.set("strings", Strings.class);
	assertEquals("nutz", El.eval(context, "strings.trim(\"    nutz   \")"));
	}}}
	   
------------------------------------------------------------------------
一些表达式的例子
	
	普通运算
    	{{{<JAVA>
    	System.out.println(El.eval("3+2*5"));
    	// 输出为  13
    	}}}

    字符串操作
	
    	{{{<JAVA>
	System.out.println(El.eval("trim(\"  abc  \")"));
    	// 输出为  abc
    	}}}
	
	Java 对象属性访问调用

    	{{{<JAVA>
    	Context context = Lang.context();
    	Pet pet = new Pet();
    	pet.setName("GFW");
    	context.set("pet",pet);
    	System.out.println(El.eval(context,"pet.name"));
    	// 输出为  GFW
    	}}}

	函数调用	
	
    	{{{<JAVA>
    	Context context = Lang.context();
    	Pet pet = new Pet();
        context.set("pet",pet);
        El.eval(context, "pet.setName('XiaoBai')");
    
    	System.out.println(El.eval(context,"pet.getName()"));
    	// 输出为  XiaoBai
    	}}}
    	
	数组访问
	   
        {{{<JAVA>
        Context context = Lang.context();
        context.set("x",Lang.array("A", "B", "C"));
        
        System.out.println(El.eval(context,"x[0].toLowerCase()"));
        // 输出为  a
        }}}
	
	列表访问
	
        {{{<JAVA>
        Context context = Lang.context();
        context.set("x",Lang.list("A", "B", "C"));
        
        System.out.println(El.eval(context, "x.get(0).toLowerCase()"));
        // 输出为  a
        }}}
	
	Map 访问
	
        {{{<JAVA>
        Context context = Lang.context();
        context.set("map",Lang.map("{x:10, y:5}"));
        
        System.out.println(El.eval(context,"map['x'] * map['y']"));
        // 输出为  50
        }}}
	
	判断
	
        {{{<JAVA>
        Context context = Lang.context();
        context.set("a",5);
        
        System.out.println(El.eval(context,"a>10"));
        // 输出为  false
        
        context.set("a",20);
        System.out.println(El.eval(context,"a>10"));
        // 输出为  true
        }}}
	
--------------------------------------------------------------------------------
通过代码注册自定义Function
	
	局部注册
	
	{{{<JAVA>
	
	@Test
    public void test_el2() throws Exception {
        El el = new El("sayhi(name)");
        Context ctx = Lang.context();
        ctx.set("name", "wendal");
        ctx.set("sayhi", getClass().getMethod("sayhi", String.class));
        assertEquals("hi,wendal", el.eval(ctx));
    }
    
    public static String sayhi(String name) {
        return "hi,"+name;
    }
	}}}
	
	
	全局注册
	
	{{{<JAVA>
	// register任意一个实现了RunMethod接口的对象
	CustomMake.me().register("ig", ioc.get(RedisIdGenerator.class));
	}}}
	
	
	
